/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/graphics.hpp>
#include <baja/graphics/scenes.hpp>
#include <baja/app/input.hpp>
#include <baja/app/scene_references.hpp>
#include <baja/app/camera.hpp>
#include <baja/app/debug_console.hpp>
#include <journal/views/interfaces.h>
#include <journal/app/constants.h>

namespace journal { namespace view {

namespace b = baja;
namespace a = baja::app;
namespace g = baja::graphics;
namespace gs = baja::graphics::scenes;

class scene_references : public iscene_references
{
    enum class state
    {
        idle,
        movingCamera,
    };

private:
    scene_references(
        const std::shared_ptr<a::iinput_capture>& inputCapture,
        const scene_state* sceneState
        ) : _state(state::idle), _inputCapture(inputCapture), _sceneState(sceneState), _visible(true)
    {
        _references = a::scene_references::create(
            sceneState->camera, 
            constants::references::fontName(), 
            constants::references::fontSize()
            );
    }

public:
    friend class std::_Ref_count_obj<scene_references>;
    static std::shared_ptr<scene_references> create(
        const std::shared_ptr<a::iinput_capture>& inputCapture,
        scene_state* sceneState
        )
    {
        std::shared_ptr<scene_references> returnValue = std::make_shared<scene_references>(inputCapture, sceneState);
        returnValue->_weak_this = returnValue;
        return returnValue;
    }

    void render()
    {
        if (_visible)
        {
            if (constants::references::showAxes())
            {
                _references->renderAxes();
                _references->renderFloor(constants::references::floorTileCount(), constants::references::floorTileSize());
            }
            
            if (constants::references::showFPS())
            {
                _references->renderFPS();
            }
        }
    }

    void setVisible(const bool visible) { _visible = visible; }
    virtual bool visible() { return _visible; }

    void onResize(const a::resize_args& args, void* context = nullptr)
    {
        // noop
    }

    void onPointer(const a::pointer_args& args, void* context = nullptr)
    {
        BAJA_VERIFY(context);
        bool* handled = static_cast<bool*>(context);

        std::shared_ptr<a::iorigin_camera> camera = 
            std::dynamic_pointer_cast<a::iorigin_camera>(_sceneState->camera);
        
        BAJA_ASSERT(camera, "only origin camera supported for scene references right now");

        if (_state != state::movingCamera) return;
        /*
        // DEMO:
        // only handle mouse moves if control key is pressed
        if (args.deviceType != a::device_type::mouse ||
            !((b::uint32)args.keys & (b::uint32)a::key_modifiers::control))
        {
            return;
        }
        */

        switch (_state)
        {
            case state::idle:
                if (args.type == a::pointer_event_type::down)
                {
                    // DEMO: _state = state::movingCamera;
                    //_inputCapture.lock()->setCapture(std::static_pointer_cast<a::iinput_observer>(_weak_this.lock()));
                    //_mouseStartPoint = args.point;
                    //DEBUG_CONSOLE_TRACE(boost::wformat(L"pt: %i") % args.point.x);
                }
                else if (args.type == a::pointer_event_type::wheel)
                {
                    camera->setDistance(camera->distance() + 4.f * (args.wheelDelta / 360.f));
                }
                break;

            case state::movingCamera:
                if (args.type == a::pointer_event_type::down)
                {
                    // DEMO: _state = state::movingCamera;
                    //_inputCapture.lock()->setCapture(std::static_pointer_cast<a::iinput_observer>(_weak_this.lock()));
                    _mouseStartPoint = args.point;
                    //DEBUG_CONSOLE_TRACE(boost::wformat(L"pt: %i") % args.point.x);
                }
                else if (args.type == a::pointer_event_type::wheel)
                {
                    camera->setDistance(camera->distance() + 4.f * (args.wheelDelta / 360.f));
                }
                else if (args.type == a::pointer_event_type::move)
                {
                    camera->rotateDeltaXYZ(
                        0.25f * (args.point.y - _mouseStartPoint.y),
                        0,
                       -0.25f * (args.point.x - _mouseStartPoint.x)
                        );
                    _mouseStartPoint = args.point;
                }
                else if (args.type == a::pointer_event_type::up)
                {
                    // DEMO: _state = state::idle;
                    //_inputCapture.lock()->releaseCapture();
                }
                else if (args.type == a::pointer_event_type::wheel)
                {
                    camera->setDistance(camera->distance() + 4.f * (args.wheelDelta / 360.f));
                }
                break;
        }

        *handled = true;
    }

    void onGesture(const a::gesture_args& args, void* context) { /*noop*/}
    void onKey(const a::key_args& args, void* context)
    {
        BAJA_VERIFY(context);
        bool* handled = static_cast<bool*>(context);
        if (args.vk == 67) // 'c'
        {
            if (_state == state::movingCamera)
            {
                _state = state::idle;
                _inputCapture.lock()->releaseCapture();
                DEBUG_CONSOLE_TRACE(L"toggling camera mode off");
                s_STOPRENDER = false;
            }
            else
            {
                s_STOPRENDER = true;
                _state = state::movingCamera;
                _inputCapture.lock()->setCapture(std::static_pointer_cast<a::iinput_observer>(_weak_this.lock()));
                DEBUG_CONSOLE_TRACE(L"toggling camera mode on");
            }
            *handled = true;
        }
    }
    void onScreenChange(const a::screen_change_args& args, void* context = nullptr) { /* noop */ };

private:

    std::weak_ptr<scene_references> _weak_this;
    const scene_state* _sceneState;
    state _state;
    m::point2d _mouseStartPoint;
    std::shared_ptr<a::scene_references> _references;
    std::weak_ptr<a::iinput_capture> _inputCapture;
    bool _visible;
};

inline std::shared_ptr<iscene_references> createSceneReferences(
    const std::shared_ptr<a::iinput_capture>& inputCapture,
    scene_state* sceneState
    )
{
    std::shared_ptr<scene_references> returnValue = scene_references::create(inputCapture, sceneState);
    return std::static_pointer_cast<iscene_references>(returnValue);
}

}} // namespace journal::view